#pragma once

#define GL_API
#ifndef ANDROID
#define GL_APIENTRY
#define GL_APIENTRYP
#endif

#include <GLES/gl.h>
#include <GLES/glext.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>

#include <stdio.h>
#include <stdlib.h>
#include "ErrorLog.h"
#include "codec_defs.h"

class GLClientState {
public:
	typedef enum {
		VERTEX_LOCATION = 0,
		NORMAL_LOCATION = 1,
		COLOR_LOCATION = 2,
		POINTSIZE_LOCATION = 3,
		TEXCOORD0_LOCATION = 4,
		TEXCOORD1_LOCATION = 5,
		TEXCOORD2_LOCATION = 6,
		TEXCOORD3_LOCATION = 7,
		TEXCOORD4_LOCATION = 8,
		TEXCOORD5_LOCATION = 9,
		TEXCOORD6_LOCATION = 10,
		TEXCOORD7_LOCATION = 11,
		MATRIXINDEX_LOCATION = 12,
		WEIGHT_LOCATION = 13,
		LAST_LOCATION = 14
	} StateLocation;

	typedef struct {
		GLint enabled;
		GLint size;
		GLenum type;
		GLsizei stride;
		void *data;
		GLuint bufferObject;
		GLenum glConst;
		unsigned int elementSize;
		bool enableDirty;  // true if any enable state has changed since last draw
		bool normalized;
	} VertexAttribState;

	typedef struct {
		int unpack_alignment;
		int pack_alignment;
	} PixelStoreState;

	enum {
		MAX_TEXTURE_UNITS = 32,
	};

public:
	GLClientState(int nLocations = CODEC_MAX_VERTEX_ATTRIBUTES);
	~GLClientState();
	int nLocations() { return m_nLocations; }
	const PixelStoreState *pixelStoreState() { return &m_pixelStore; }
	int setPixelStore(GLenum param, GLint value);
	GLuint currentArrayVbo() { return m_currentArrayVbo; }
	GLuint currentIndexVbo() { return m_currentIndexVbo; }
	void enable(int location, int state);
	void setState(int  location, int size, GLenum type, GLboolean normalized, GLsizei stride, const void *data);
	void setBufferObject(int location, GLuint id);
	const VertexAttribState  *getState(int location);
	const VertexAttribState  *getStateAndEnableDirty(int location, bool *enableChanged);
	int getLocation(GLenum loc);
	void setActiveTexture(int texUnit) { m_activeTexture = texUnit; };
	int getActiveTexture() const { return m_activeTexture; }
	void setMaxVertexAttribs(int val) {
		m_maxVertexAttribs = val;
		m_maxVertexAttribsDirty = false;
	}

	void unBindBuffer(GLuint id)
	{
		if (m_currentArrayVbo == id) m_currentArrayVbo = 0;
		else if (m_currentIndexVbo == id) m_currentIndexVbo = 0;
	}

	int bindBuffer(GLenum target, GLuint id)
	{
		int err = 0;
		switch (target) {
		case GL_ARRAY_BUFFER:
			m_currentArrayVbo = id;
			break;
		case GL_ELEMENT_ARRAY_BUFFER:
			m_currentIndexVbo = id;
			break;
		default:
			err = -1;
		}
		return err;
	}

	int getBuffer(GLenum target)
	{
		int ret = 0;
		switch (target) {
		case GL_ARRAY_BUFFER:
			ret = m_currentArrayVbo;
			break;
		case GL_ELEMENT_ARRAY_BUFFER:
			ret = m_currentIndexVbo;
			break;
		default:
			ret = -1;
		}
		return ret;
	}
	size_t pixelDataSize(GLsizei width, GLsizei height, GLenum format, GLenum type, int pack) const;

	void setCurrentProgram(GLint program) { m_currentProgram = program; }
	GLint currentProgram() const { return m_currentProgram; }

	/* OES_EGL_image_external
	*
	* These functions manipulate GL state which interacts with the
	* OES_EGL_image_external extension, to support client-side emulation on
	* top of host implementations that don't have it.
	*
	* Most of these calls should only be used with TEXTURE_2D or
	* TEXTURE_EXTERNAL_OES texture targets; TEXTURE_CUBE_MAP or other extension
	* targets should bypass this. An exception is bindTexture(), which should
	* see all glBindTexture() calls for any target.
	*/

	// glActiveTexture(GL_TEXTURE0 + i)
	// Sets the active texture unit. Up to MAX_TEXTURE_UNITS are supported.
	GLenum setActiveTextureUnit(GLenum texture);
	GLenum getActiveTextureUnit() const;

	// glEnable(GL_TEXTURE_(2D|EXTERNAL_OES))
	void enableTextureTarget(GLenum target);

	// glDisable(GL_TEXTURE_(2D|EXTERNAL_OES))
	void disableTextureTarget(GLenum target);

	// Implements the target priority logic:
	// * Return GL_TEXTURE_EXTERNAL_OES if enabled, else
	// * Return GL_TEXTURE_2D if enabled, else
	// * Return the allDisabled value.
	// For some cases passing GL_TEXTURE_2D for allDisabled makes callee code
	// simpler; for other cases passing a recognizable enum like GL_ZERO or
	// GL_INVALID_ENUM is appropriate.
	GLenum getPriorityEnabledTarget(GLenum allDisabled) const;

	// glBindTexture(GL_TEXTURE_*, ...)
	// Set the target binding of the active texture unit to texture. Returns
	// GL_NO_ERROR on success or GL_INVALID_OPERATION if the texture has
	// previously been bound to a different target. If firstUse is not NULL,
	// it is set to indicate whether this is the first use of the texture.
	// For accurate error detection, bindTexture should be called for *all*
	// targets, not just 2D and EXTERNAL_OES.
	GLenum bindTexture(GLenum target, GLuint texture, GLboolean* firstUse);

	// Return the texture currently bound to GL_TEXTURE_(2D|EXTERNAL_OES).
	GLuint getBoundTexture(GLenum target) const;

	// glDeleteTextures(...)
	// Remove references to the to-be-deleted textures.
	void deleteTextures(GLsizei n, const GLuint* textures);

	void setNumTexture(GLint num);

private:
	PixelStoreState m_pixelStore;
	VertexAttribState *m_states;
	int m_maxVertexAttribs;
	bool m_maxVertexAttribsDirty;
	int m_nLocations;
	GLuint m_currentArrayVbo;
	GLuint m_currentIndexVbo;
	int m_activeTexture;
	GLint m_currentProgram;
	GLint m_num_texture_units;

	bool validLocation(int location) { return (location >= 0 && location < m_nLocations); }

	enum TextureTarget {
		TEXTURE_2D = 0,
		TEXTURE_EXTERNAL = 1,
		TEXTURE_TARGET_COUNT
	};
	struct TextureUnit {
		unsigned int enables;
		GLuint texture[TEXTURE_TARGET_COUNT];
	};
	struct TextureRec {
		GLuint id;
		GLenum target;
	};
	struct TextureState {
		TextureUnit unit[MAX_TEXTURE_UNITS];
		TextureUnit* activeUnit;
		TextureRec* textures;
		GLuint numTextures;
		GLuint allocTextures;
	};
	TextureState m_tex;

	static int compareTexId(const void* pid, const void* prec);
	TextureRec* addTextureRec(GLuint id, GLenum target);

public:
	void getClientStatePointer(GLenum pname, GLvoid** params);

	template <class T>
	int getVertexAttribParameter(GLuint index, GLenum param, T *ptr)
	{
		bool handled = true;
		const VertexAttribState *vertexAttrib = getState(index);
		if (vertexAttrib == NULL) {
			ERR("getVeterxAttriParameter for non existant index %d\n", index);
			// set gl error;
			return handled;
		}

		switch (param) {
		case GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING:
			*ptr = (T)(vertexAttrib->bufferObject);
			break;
		case GL_VERTEX_ATTRIB_ARRAY_ENABLED:
			*ptr = (T)(vertexAttrib->enabled);
			break;
		case GL_VERTEX_ATTRIB_ARRAY_SIZE:
			*ptr = (T)(vertexAttrib->size);
			break;
		case GL_VERTEX_ATTRIB_ARRAY_STRIDE:
			*ptr = (T)(vertexAttrib->stride);
			break;
		case GL_VERTEX_ATTRIB_ARRAY_TYPE:
			*ptr = (T)(vertexAttrib->type);
			break;
		case GL_VERTEX_ATTRIB_ARRAY_NORMALIZED:
			*ptr = (T)(vertexAttrib->normalized);
			break;
		case GL_CURRENT_VERTEX_ATTRIB:
			handled = false;
			break;
		default:
			handled = false;
			ERR("unknown vertex-attrib parameter param %d\n", param);
		}
		return handled;
	}

	template <class T>
	bool getClientStateParameter(GLenum param, T* ptr)
	{
		bool isClientStateParam = false;
		switch (param) {
		case GL_CLIENT_ACTIVE_TEXTURE: {
			GLint tex = getActiveTexture() + GL_TEXTURE0;
			*ptr = tex;
			isClientStateParam = true;
			break;
		}
		case GL_VERTEX_ARRAY_SIZE: {
			const GLClientState::VertexAttribState *state = getState(GLClientState::VERTEX_LOCATION);
			*ptr = state->size;
			isClientStateParam = true;
			break;
		}
		case GL_VERTEX_ARRAY_TYPE: {
			const GLClientState::VertexAttribState *state = getState(GLClientState::VERTEX_LOCATION);
			*ptr = state->type;
			isClientStateParam = true;
			break;
		}
		case GL_VERTEX_ARRAY_STRIDE: {
			const GLClientState::VertexAttribState *state = getState(GLClientState::VERTEX_LOCATION);
			*ptr = state->stride;
			isClientStateParam = true;
			break;
		}
		case GL_COLOR_ARRAY_SIZE: {
			const GLClientState::VertexAttribState *state = getState(GLClientState::COLOR_LOCATION);
			*ptr = state->size;
			isClientStateParam = true;
			break;
		}
		case GL_COLOR_ARRAY_TYPE: {
			const GLClientState::VertexAttribState *state = getState(GLClientState::COLOR_LOCATION);
			*ptr = state->type;
			isClientStateParam = true;
			break;
		}
		case GL_COLOR_ARRAY_STRIDE: {
			const GLClientState::VertexAttribState *state = getState(GLClientState::COLOR_LOCATION);
			*ptr = state->stride;
			isClientStateParam = true;
			break;
		}
		case GL_NORMAL_ARRAY_TYPE: {
			const GLClientState::VertexAttribState *state = getState(GLClientState::NORMAL_LOCATION);
			*ptr = state->type;
			isClientStateParam = true;
			break;
		}
		case GL_NORMAL_ARRAY_STRIDE: {
			const GLClientState::VertexAttribState *state = getState(GLClientState::NORMAL_LOCATION);
			*ptr = state->stride;
			isClientStateParam = true;
			break;
		}
		case GL_TEXTURE_COORD_ARRAY_SIZE: {
			const GLClientState::VertexAttribState *state = getState(getActiveTexture() + GLClientState::TEXCOORD0_LOCATION);
			*ptr = state->size;
			isClientStateParam = true;
			break;
		}
		case GL_TEXTURE_COORD_ARRAY_TYPE: {
			const GLClientState::VertexAttribState *state = getState(getActiveTexture() + GLClientState::TEXCOORD0_LOCATION);
			*ptr = state->type;
			isClientStateParam = true;
			break;
		}
		case GL_TEXTURE_COORD_ARRAY_STRIDE: {
			const GLClientState::VertexAttribState *state = getState(getActiveTexture() + GLClientState::TEXCOORD0_LOCATION);
			*ptr = state->stride;
			isClientStateParam = true;
			break;
		}
		case GL_POINT_SIZE_ARRAY_TYPE_OES: {
			const GLClientState::VertexAttribState *state = getState(GLClientState::POINTSIZE_LOCATION);
			*ptr = state->type;
			isClientStateParam = true;
			break;
		}
		case GL_POINT_SIZE_ARRAY_STRIDE_OES: {
			const GLClientState::VertexAttribState *state = getState(GLClientState::POINTSIZE_LOCATION);
			*ptr = state->stride;
			isClientStateParam = true;
			break;
		}
		case GL_MATRIX_INDEX_ARRAY_SIZE_OES: {
			const GLClientState::VertexAttribState *state = getState(GLClientState::MATRIXINDEX_LOCATION);
			*ptr = state->size;
			isClientStateParam = true;
			break;
		}
		case GL_MATRIX_INDEX_ARRAY_TYPE_OES: {
			const GLClientState::VertexAttribState *state = getState(GLClientState::MATRIXINDEX_LOCATION);
			*ptr = state->type;
			isClientStateParam = true;
			break;
		}
		case GL_MATRIX_INDEX_ARRAY_STRIDE_OES: {
			const GLClientState::VertexAttribState *state = getState(GLClientState::MATRIXINDEX_LOCATION);
			*ptr = state->stride;
			isClientStateParam = true;
			break;
		}
		case GL_WEIGHT_ARRAY_SIZE_OES: {
			const GLClientState::VertexAttribState *state = getState(GLClientState::WEIGHT_LOCATION);
			*ptr = state->size;
			isClientStateParam = true;
			break;
		}
		case GL_WEIGHT_ARRAY_TYPE_OES: {
			const GLClientState::VertexAttribState *state = getState(GLClientState::WEIGHT_LOCATION);
			*ptr = state->type;
			isClientStateParam = true;
			break;
		}
		case GL_WEIGHT_ARRAY_STRIDE_OES: {
			const GLClientState::VertexAttribState *state = getState(GLClientState::WEIGHT_LOCATION);
			*ptr = state->stride;
			isClientStateParam = true;
			break;
		}
		case GL_VERTEX_ARRAY_BUFFER_BINDING: {
			const GLClientState::VertexAttribState *state = getState(GLClientState::VERTEX_LOCATION);
			*ptr = state->bufferObject;
			isClientStateParam = true;
			break;
		}
		case GL_NORMAL_ARRAY_BUFFER_BINDING: {
			const GLClientState::VertexAttribState *state = getState(GLClientState::NORMAL_LOCATION);
			*ptr = state->bufferObject;
			isClientStateParam = true;
			break;
		}
		case GL_COLOR_ARRAY_BUFFER_BINDING: {
			const GLClientState::VertexAttribState *state = getState(GLClientState::COLOR_LOCATION);
			*ptr = state->bufferObject;
			isClientStateParam = true;
			break;
		}
		case GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING: {
			const GLClientState::VertexAttribState *state = getState(getActiveTexture() + GLClientState::TEXCOORD0_LOCATION);
			*ptr = state->bufferObject;
			isClientStateParam = true;
			break;
		}
		case GL_POINT_SIZE_ARRAY_BUFFER_BINDING_OES: {
			const GLClientState::VertexAttribState *state = getState(GLClientState::POINTSIZE_LOCATION);
			*ptr = state->bufferObject;
			isClientStateParam = true;
			break;
		}
		case GL_MATRIX_INDEX_ARRAY_BUFFER_BINDING_OES: {
			const GLClientState::VertexAttribState *state = getState(GLClientState::MATRIXINDEX_LOCATION);
			*ptr = state->bufferObject;
			isClientStateParam = true;
			break;
		}
		case GL_WEIGHT_ARRAY_BUFFER_BINDING_OES: {
			const GLClientState::VertexAttribState *state = getState(GLClientState::WEIGHT_LOCATION);
			*ptr = state->bufferObject;
			isClientStateParam = true;
			break;
		}
		case GL_ARRAY_BUFFER_BINDING: {
			int buffer = getBuffer(GL_ARRAY_BUFFER);
			*ptr = buffer;
			isClientStateParam = true;
			break;
		}
		case GL_ELEMENT_ARRAY_BUFFER_BINDING: {
			int buffer = getBuffer(GL_ELEMENT_ARRAY_BUFFER);
			*ptr = buffer;
			isClientStateParam = true;
			break;
		}
		case GL_MAX_VERTEX_ATTRIBS: {
			if (m_maxVertexAttribsDirty) {
				isClientStateParam = false;
			}
			else {
				*ptr = m_maxVertexAttribs;
				isClientStateParam = true;
			}
			break;
		}
		}
		return isClientStateParam;
	}

};